
#include <gtest/gtest.h>
#include <chrono>
#include <thread>
#include <fstream>
#include <sstream>
#include <config.h>
#include <unordered_set>
#include "log/datacollector.hh"
#include "log/diagconfig.h"
#include "log/diagosapi.h"
#include "test_main.h"
#include "test_comm.h"

class TestDiagFixture : public testing::Test
{
public:
    std::string cfgfile = "/tmp/logcfg.ini";
    virtual void SetUp() { ASSERT_EQ(WaitDiagCollectingStop(30000), 0) << "Timeout: Wait Diag Collecting..."; }

    virtual void TearDown()
    {
        if (access(cfgfile.c_str(), F_OK))
        {
            remove(cfgfile.c_str());
        }
        ASSERT_EQ(WaitDiagCollectingStop(30000), 0) << "Timeout: Wait Diag Collecting...";
    }

    static int32_t WaitDiagCollectingStop(uint64_t msTimeout)
    {
        auto callback = []() {
            int32_t ret = zucDiagIsCollectingData();
            if (ret == -1)
            {
                std::cout << "Not intialized...\n";
            }

            if (ret == 0)
                return true;

            GtestLogSleep(1000);
            return false;
        };
        return ::WaitCondTrue(callback, msTimeout);
    }

    static int32_t WaitDiagCollectingStart(uint64_t msTimeout)
    {
        auto callback = []() {
            int32_t ret = zucDiagIsCollectingData();
            if (ret == -1)
            {
                std::cout << "Not intialized...\n";
            }

            if (ret == 1)
                return true;

            GtestLogSleep(1);
            return false;
        };
        return ::WaitCondTrue(callback, msTimeout);
    }

    static int32_t CreateFile(const std::string& filename, const std::string& content)
    {
        std::fstream fs;
        fs.open(filename, std::ios::out);
        if (!fs)
        {
            return -1;
        }
        else
        {
            fs << content << "\n";
        }
        fs.close();
        return 0;
    }

    typedef struct
    {
        uint32_t basedur;
        uint32_t maxdur;
        uint32_t stepdur;
        uint32_t maxbytes;
    } ConfigType;

    static int32_t CreateUserSettingsConfig(const std::string& filename, const ConfigType& config)
    {
        std::stringstream ss;
        ss << "[DIAGNOSIS]\n"
           << "DIAG_BASE_DUR_KEY=" << config.basedur << "\n"
           << "DIAG_MAX_DUR_KEY=" << config.maxdur << "\n"
           << "DIAG_STEP_KEY=" << config.stepdur << "\n"
           << "DIAG_MAX_FILE_BYTES_KEY=" << config.maxbytes << "\n";

        return CreateFile(filename, ss.str());
    }

    static const std::string DIAG_TARGZ_BASE_DIR;
    static const std::string LOGTEST_RES_DIR;
};

const std::string TestDiagFixture::DIAG_TARGZ_BASE_DIR = ZUC_DIAGNOSIS_DIR;
// TestDiagFixture::
const std::string TestDiagFixture::LOGTEST_RES_DIR = "/tmp";

class DiagFileChecker
{
public:
    int32_t Start()
    {
        // 存在diagnosis目录不存在的情况
        if (DiagOSAPI::ListFile(TestDiagFixture::DIAG_TARGZ_BASE_DIR, origFileList, false))
        {
            origFileList.clear();
        }
        return 0;
    }

    int32_t Stop() { return DiagOSAPI::ListFile(TestDiagFixture::DIAG_TARGZ_BASE_DIR, curFileList, false); }

    int32_t Check()
    {
        if (origFileList.size() + 1 != curFileList.size())
        {
            return -1;
        }

        std::string filename;
        if (GetDiagFileName(filename) != 0)
        {
            return -1;
        }

        std::cout << "New Diag File: " << filename << "\n";
        std::string outdir;
        if (Uncompress(filename, outdir))
        {
            return -1;
        }

        // TODO: 检查诊断目录内各个文件是否存在

        DiagOSAPI::RemoveF(outdir);
        return 0;
    }

    int32_t Uncompress(const std::string& targzFullPath, std::string& outdir)
    {
        if (EndsWith(targzFullPath, ".tar.gz"))
        {
            return -1;
        }

        auto p = targzFullPath.find_last_of("/");
        auto targzDir = targzFullPath.substr(0, p);
        outdir = targzFullPath.substr(0, targzFullPath.size() - sizeof(".tar.gz") + 1);
        std::string cmd = std::string("tar") + " zxvf " + targzFullPath + " -C " + targzDir;
        std::cout << "Run cmd " << cmd << "\n";
        return system(cmd.c_str());
    }

    int32_t EndsWith(const std::string& src, const std::string& tgt)
    {
        if (src.size() < tgt.size())
        {
            return -1;
        }

        auto pTgt = tgt.crbegin();
        for (auto p = src.crbegin(); p != src.crend(); p++)
        {
            if (pTgt == tgt.crend())
            {
                return 0;
            }

            if (*pTgt != *p)
            {
                return -1;
            }

            pTgt++;
        }

        return -1;
    }

private:
    int32_t GetDiagFileName(std::string& filename)
    {
        std::unordered_set<std::string> origSet;
        for (auto& p : origFileList) { origSet.insert(p); }

        for (auto& p : curFileList)
        {
            if (origSet.find(p) == origSet.end())
            {
                filename = p;
                return 0;
            }
        }
        return -1;
    }

private:
    std::vector<std::string> origFileList;
    std::vector<std::string> curFileList;
};

TEST_F(TestDiagFixture, TestNoDiagnosis) { EXPECT_EQ(zucDiagEcode(0x1), -1); }

TEST_F(TestDiagFixture, TestDiagnosis)
{
    EXPECT_EQ(zucDiagEcode(0x00103211), 0);
    EXPECT_EQ(WaitDiagCollectingStart(10), 0);
    EXPECT_EQ(zucDiagCollectData(0), 0);
}

TEST_F(TestDiagFixture, TestDiagnosisWithoutDiagDir)
{
    if (IsDirExists(DIAG_TARGZ_BASE_DIR))
    {
        DiagOSAPI::RemoveF(DIAG_TARGZ_BASE_DIR);
    }

    EXPECT_FALSE(IsDirExists(DIAG_TARGZ_BASE_DIR));
    EXPECT_EQ(zucDiagEcode(0x00103211), 0);
    EXPECT_EQ(WaitDiagCollectingStart(10), 0);
    EXPECT_EQ(zucDiagCollectData(0), 0);
}

TEST_F(TestDiagFixture, TestDiagDataCtrl)
{
    EXPECT_EQ(zucDiagCollectData(1), 0);
    EXPECT_EQ(WaitDiagCollectingStart(10), 0);
    EXPECT_EQ(zucDiagIsCollectingData(), 1);
    EXPECT_EQ(zucDiagCollectData(0), 0);

    EXPECT_EQ(WaitDiagCollectingStop(30000), 0);
    EXPECT_EQ(zucDiagIsCollectingData(), 0);
}

TEST_F(TestDiagFixture, TestDiagInitConfig)
{
    DiagConfig diagConfig;
    constexpr uint32_t basedur = 20;
    constexpr uint32_t maxdur = 60;
    constexpr uint32_t stepdur = 5;
    constexpr uint32_t maxDiagBytes = 0xFFFF;

    ASSERT_EQ(CreateUserSettingsConfig(cfgfile, {basedur, maxdur, stepdur, maxDiagBytes}), 0);
    EXPECT_EQ(diagConfig.Init(cfgfile), 0);

    EXPECT_EQ(diagConfig.BaseDur(), basedur);
    EXPECT_EQ(diagConfig.MaxDur(), maxdur);
    EXPECT_EQ(diagConfig.StepDur(), stepdur);
    EXPECT_EQ(diagConfig.MaxDiagByte(), maxDiagBytes);
}

TEST_F(TestDiagFixture, TestDiagInitConfigNotExist)
{
    DiagConfig diagConfig;
    std::string usersettingsPath = "/NOT_EXIST.ini";
    EXPECT_EQ(diagConfig.Init(usersettingsPath), -1);
    EXPECT_EQ(diagConfig.BaseDur(), DiagConfig::DEF_DIAG_BASE_DUR);
    EXPECT_EQ(diagConfig.MaxDur(), DiagConfig::DEF_DIAG_MAX_DUR);
    EXPECT_EQ(diagConfig.StepDur(), DiagConfig::DEF_DIAG_STEP_DUR);
    EXPECT_EQ(diagConfig.MaxDiagByte(), DiagConfig::DEF_DIAG_MAX_FILE_BYTES);
}

TEST_F(TestDiagFixture, TestDiagInitConfigOutOfRange)
{
    DiagConfig diagConfig;
    constexpr uint32_t basedur = -1;
    constexpr uint32_t maxdur = -1;
    constexpr uint32_t stepdur = -1;
    constexpr uint32_t maxDiagBytes = -1;

    std::string usersettingsPath = std::string(LOGTEST_RES_DIR) + "/usersettings.ini";
    ASSERT_EQ(CreateUserSettingsConfig(usersettingsPath, {basedur, maxdur, stepdur, maxDiagBytes}), 0);

    EXPECT_EQ(diagConfig.Init(usersettingsPath), -1);
    EXPECT_EQ(diagConfig.BaseDur(), DiagConfig::DEF_DIAG_BASE_DUR);
    EXPECT_EQ(diagConfig.MaxDur(), DiagConfig::DEF_DIAG_MAX_DUR);
    EXPECT_EQ(diagConfig.StepDur(), DiagConfig::DEF_DIAG_STEP_DUR);
    EXPECT_EQ(diagConfig.MaxDiagByte(), DiagConfig::DEF_DIAG_MAX_FILE_BYTES);
}

TEST_F(TestDiagFixture, TestDiagCollectData)
{
    constexpr uint32_t BASE_DUR = 2;
    constexpr uint32_t MAX_DUR = 10;
    constexpr uint32_t STEP_DUR = 3;

    DiagFileChecker checker;
    EXPECT_EQ(checker.Start(), 0);

    ASSERT_EQ(zucDiagConfig(BASE_DUR, STEP_DUR, MAX_DUR), 0);

    int id = 0;
    bool stop_flag = false;
    std::thread t([&id, &stop_flag]() {
        int index = 0;
        while (stop_flag == false)
        {
            if (index == 1000)
            {
                ASSERT_EQ(zucDiagEcode(0x000F0034), 0);
            }
            zucDiagBusMsg msg;
            msg.id = index;
            msg.size = 1;
            msg.data[0] = index + 2;
            msg.timestamp_ = index;
            zucDiagBus(msg);
            usleep(1000);
            index++;
        }
    });
    ASSERT_EQ(WaitDiagCollectingStart(3000), 0);
    ASSERT_EQ(WaitDiagCollectingStop((BASE_DUR + 30) * 1000), 0);  //默认诊断时间60秒,等待90秒,30秒余量
    stop_flag = true;
    t.join();
    EXPECT_EQ(checker.Stop(), 0);
    EXPECT_EQ(checker.Check(), 0);
}

TEST_F(TestDiagFixture, TestDiagRecordTime)
{
    constexpr uint32_t BASE_DUR = 2;
    constexpr uint32_t MAX_DUR = 10;
    constexpr uint32_t STEP_DUR = 3;

    DiagFileChecker checker;
    EXPECT_EQ(checker.Start(), 0);

    ASSERT_EQ(zucDiagConfig(BASE_DUR, STEP_DUR, MAX_DUR), 0);

    auto st = std::chrono::steady_clock::now();
    ASSERT_EQ(zucDiagEcode(0x000F0034), 0);
    ASSERT_EQ(WaitDiagCollectingStart(10), 0);
    ASSERT_EQ(WaitDiagCollectingStop((BASE_DUR + 30) * 1000), 0);  //默认诊断时间60秒,等待90秒,30秒余量
    auto ed = std::chrono::steady_clock::now();
    auto secCnt = std::chrono::duration_cast<std::chrono::seconds>(ed - st);
    EXPECT_NEAR(secCnt.count(), BASE_DUR, 30);  // 30 为文件压缩时间

    EXPECT_EQ(checker.Stop(), 0);
    EXPECT_EQ(checker.Check(), 0);
}

TEST_F(TestDiagFixture, TestDiagRecordTimeWithDelay)
{
    constexpr uint32_t BASE_DUR = 2;
    constexpr uint32_t MAX_DUR = 10;
    constexpr uint32_t STEP_DUR = 3;

    DiagFileChecker checker;
    EXPECT_EQ(checker.Start(), 0);

    ASSERT_EQ(zucDiagConfig(BASE_DUR, STEP_DUR, MAX_DUR), 0);

    constexpr int32_t DELAY_TIMES = 2;
    auto st = std::chrono::steady_clock::now();
    ASSERT_EQ(zucDiagEcode(0x000F0034), 0);

    for (int i = 0; i < DELAY_TIMES; i++) { ASSERT_EQ(zucDiagEcode(0x000F0034), 0); }

    ASSERT_EQ(WaitDiagCollectingStart(10), 0);

    constexpr int32_t WAIT_TIME = DELAY_TIMES * STEP_DUR + BASE_DUR + 30;
    ASSERT_EQ(WaitDiagCollectingStop(WAIT_TIME * 1000), 0);

    // 检查诊断时间
    auto ed = std::chrono::steady_clock::now();
    auto secCnt = std::chrono::duration_cast<std::chrono::seconds>(ed - st);
    EXPECT_NEAR(secCnt.count(), DELAY_TIMES * STEP_DUR + BASE_DUR, 30);  // 30 为文件压缩时间

    EXPECT_EQ(checker.Stop(), 0);
    EXPECT_EQ(checker.Check(), 0);
}

TEST_F(TestDiagFixture, TestDiagMaxTime)
{
    constexpr uint32_t BASE_DUR = 2;
    constexpr uint32_t MAX_DUR = 10;
    constexpr uint32_t STEP_DUR = 3;
    constexpr int32_t DELAY_TIMES = 20;
    auto st = std::chrono::steady_clock::now();

    DiagFileChecker checker;
    EXPECT_EQ(checker.Start(), 0);

    ASSERT_EQ(zucDiagConfig(BASE_DUR, STEP_DUR, MAX_DUR), 0);

    ASSERT_EQ(zucDiagEcode(0x000F0034), 0);
    for (int i = 0; i < DELAY_TIMES; i++) { ASSERT_EQ(zucDiagEcode(0x000F0034), 0); }

    ASSERT_EQ(WaitDiagCollectingStart(10), 0);
    constexpr int32_t WAIT_TIME = DELAY_TIMES * STEP_DUR + BASE_DUR + 30;
    ASSERT_EQ(WaitDiagCollectingStop(WAIT_TIME * 1000), 0);

    // 检查诊断时间
    auto ed = std::chrono::steady_clock::now();
    auto secCnt = std::chrono::duration_cast<std::chrono::seconds>(ed - st);
    EXPECT_NEAR(secCnt.count(), MAX_DUR, 30);  // 30 为文件压缩时间

    EXPECT_EQ(checker.Stop(), 0);
    EXPECT_EQ(checker.Check(), 0);
}
